/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.view;

import org.jfonia.view.panels.ViewPanel;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.jfonia.commands.AddMusicalElementCommand;
import org.jfonia.commands.RemoveMusicalElementCommand;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.numerical.DoubleMaxRelation;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.constants.ViewConstants;
import org.jfonia.notation.IStaffElementSequenceObserver;
import org.jfonia.model.ISequenceObserver;
import org.jfonia.notation.StaffElementSequence;
import org.jfonia.model.PerformanceNode;
import org.jfonia.model.Sequence;
import org.jfonia.model.ToneSequence;
import org.jfonia.model.elements.Beat;
import org.jfonia.model.elements.Chord;
import org.jfonia.model.elements.Clef;
import org.jfonia.model.elements.IStaffElement;
import org.jfonia.model.elements.KeySignature;
import org.jfonia.model.elements.Lyric;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.model.elements.TimeSignature;
import org.jfonia.model.elements.Tone;
import org.jfonia.view.elements.AbstractField;
import org.jfonia.view.elements.ChordField;
import org.jfonia.view.elements.IElement;
import org.jfonia.view.elements.LyricField;
import org.jfonia.view.elements.StaffPart;
import org.jfonia.notation.Tie;
import org.jfonia.view.panels.ViewLayerPanel;
import org.jfonia.view.symbols.TieSymbol;
import percussiongenerator.model.Pair;

/**
 *
 * @author Rik Bauwens
 */
public class SequenceView implements ISequenceObserver, IStaffElementSequenceObserver
{
        private ViewPanel viewPanel;
        private ViewLayerPanel viewLayerPanel;
        private StaffView staffView;
        private boolean multiSequence;

        private RelationBuilder relationBuilder;
        private MutableValueNode<Double> yNode;
        private MutableValueNode<Double> heightNode;
        private MutableValueNode<Double> staffBase;
        private MutableValueNode<Double> postStaffBase;

        private List<Sequence> sequences;
        private StaffElementSequence staffElementSequence;
        //private TreeMap<Integer, IElement[]> elementMap;
        private Map<Integer, Map<Tie, TieSymbol>> ties;

        public SequenceView(ViewPanel viewPanel, ViewLayerPanel viewLayerPanel,
                            StaffView staffView, boolean multiSequence,
                            RelationBuilder relationBuilder,
                            MutableValueNode<Double> yNode,
                            MutableValueNode<Double> heightNode)
        {
                this.viewPanel = viewPanel;
                this.viewLayerPanel = viewLayerPanel;
                this.staffView = staffView;
                this.multiSequence = multiSequence;
                this.relationBuilder = relationBuilder;

                this.yNode = new BasicValueNode<Double>(0.0);
                new Equal(yNode, this.yNode);
                this.heightNode = new BasicValueNode<Double>(0.0);
                new Equal(heightNode, this.heightNode);

                staffBase = new BasicValueNode<Double>(0.0);
                //Ensure that the staffbase is at least the height of the staff itself
                MutableValueNode<Double> basicStaffBase = new BasicValueNode<Double>
                        ((ViewConstants.STAFF_LINE_COUNT - 1) * ViewConstants.STAFF_LINE_SPACING);
                new DoubleMaxRelation(staffBase, basicStaffBase);
                postStaffBase = new BasicValueNode<Double>(0.0);

                sequences = new ArrayList<Sequence>();

                //elementMap = new TreeMap<Integer, IElement[]>();
                ties = new HashMap<Integer, Map<Tie, TieSymbol>>();

                staffElementSequence = new StaffElementSequence();
                staffElementSequence.addObserver(this);
        }

        public IElement getElement(int beginTick, int index)
        {
                return relationBuilder.getMapper(this).getElement(beginTick, index);
        }

        public int getLength()
        {
                if(sequences.isEmpty())
                        return 0;
                //All sequences (must) have the same length
                return sequences.iterator().next().getMusicalLength();
        }

        public boolean isMultiSequence()
        {
                return multiSequence;
        }

        public StaffElementSequence getStaffElementSequence()
        {
                return staffElementSequence;
        }

        public Sequence getToneSequence()
        {
                if(staffElementSequence == null)
                        return null;
                return staffElementSequence.getToneSequence();
                //return getSequence(Tone.class);
        }
        
        public Sequence getClefSequence()
        {
                return getSequence(Clef.class);
        }

        public Sequence getKeySignatureSequence()
        {
                return getSequence(KeySignature.class);
        }

        public Sequence getTimeSignatureSequence()
        {
                return getSequence(TimeSignature.class);
        }

        public Sequence getChordSequence()
        {
                return getSequence(Chord.class);
        }

        public Sequence getLyricSequence()
        {
                return getSequence(Lyric.class);
        }

        private Sequence getSequence(Class type)
        {
                int i = 0;
                while(i < sequences.size() &&
                        !sequences.get(i).getTemplateClass().equals(type))
                        i++;
                if(i < sequences.size())
                        return sequences.get(i);
                return null;
        }

        public void addSequence(Sequence sequence)
        {
                if((!multiSequence && sequences.isEmpty()) //Unisequence (chord, lyrics)
                   || (multiSequence && !sequences.contains(sequence))) //Multisequence (tone, clef, keysignature, timesignature)
                {
                        if(sequence instanceof ToneSequence)
                        {
                                staffElementSequence.setToneSequence((ToneSequence)sequence);
                        }
                        else if(sequence.getTemplateClass().equals(Beat.class))
                        {
                                staffElementSequence.setBeatSequence(sequence);
                        }
                        else
                        {
                                if(sequence.getTemplateClass().equals(Clef.class))
                                        staffElementSequence.setClefSequence(sequence);
                                sequences.add(sequence);
                                sequence.addObserver(this);                                
                                for(int i = 0; i < sequence.size(); i++)
                                        add((MusicalElement)sequence.getIntervalFromIndex(i));
                        }
                }
        }

        public void removeSequence(Sequence sequence)
        {
                if(sequence.getTemplateClass().equals(Tone.class))
                {
                        staffElementSequence.setToneSequence(null);
                }
                else if(sequence.getTemplateClass().equals(Beat.class))
                {
                        staffElementSequence.setBeatSequence(null);
                }
                else if(sequences.contains(sequence))
                {
                        sequences.remove(sequence);
                        sequence.removeObserver(this);                        
                        //relationBuilder.removeSequence(this, sequence);
                }
        }

        public void addMusicalElement(int beginTick, MusicalElement musicalElement)
        {
                if(musicalElement instanceof Clef)
                        viewPanel.getLeadSheet().execute(new AddMusicalElementCommand(
                                getClefSequence(), beginTick, musicalElement));
                else if(musicalElement instanceof KeySignature)
                        viewPanel.getLeadSheet().execute(new AddMusicalElementCommand(
                                getKeySignatureSequence(), beginTick, musicalElement));
                else if(musicalElement instanceof TimeSignature)
                        viewPanel.getLeadSheet().execute(new AddMusicalElementCommand(
                                getTimeSignatureSequence(), beginTick, musicalElement));
                else if(musicalElement instanceof IStaffElement)
                        viewPanel.getLeadSheet().execute(new AddMusicalElementCommand(
                                staffElementSequence, beginTick, musicalElement));
        }

        public void removeMusicalElement(MusicalElement musicalElement)
        {
                if(musicalElement instanceof IStaffElement)
                        viewPanel.getLeadSheet().execute(
                                new RemoveMusicalElementCommand(staffElementSequence, musicalElement));
                        //staffElementSequence.removeStaffElement((IStaffElement) musicalElement);
        }

        private void add(MusicalElement musicalElement)
        {
                IElement element = null;
                MutableValueNode<Double> frameXNode = new BasicValueNode<Double>(0.0);
                MutableValueNode<Double> frameWidthNode = new BasicValueNode<Double>(0.0);
                if(musicalElement instanceof Chord)
                        element = createChordField((Chord) musicalElement, 
                                frameXNode, frameWidthNode);
                else if(musicalElement instanceof Lyric)
                        element = createLyricField((Lyric) musicalElement, 
                                frameXNode, frameWidthNode);
                else if(musicalElement instanceof Tie)
                        createTie((Tie) musicalElement);
                else if(musicalElement instanceof IStaffElement)
                        element = createStaffSymbol((IStaffElement) musicalElement, 
                                frameXNode, frameWidthNode);

                if(element != null) {
                        relationBuilder.getMapper(this).add(element);
                        //if(!elementMap.containsKey(musicalElement.getBegin()))
                        //        elementMap.put(musicalElement.getBegin(), new IElement[2]);
                        //elementMap.get(musicalElement.getBegin())[getIndex(musicalElement)] = element;
                        viewPanel.add((Component) element);
                }
        }

        private void remove(MusicalElement musicalElement)
        {
                if(musicalElement instanceof Tie)
                        removeTie((Tie) musicalElement);
                else
                {
                        int tick = musicalElement.getBegin();
                        int index = getIndex(musicalElement);
                        //TODO: remove oplossen
                        //if(elementMap.get(tick) == null)
                        //        return;
                        IElement element = relationBuilder.getMapper(this).getElement(musicalElement);
                        //if(element == null)
                        //        return;
                        if(musicalElement instanceof IStaffElement)
                                ((StaffPart) element).removeStaffElement((IStaffElement) musicalElement);
                        else
                                ((AbstractField) element).removeMusicalElement();

                        if(element.isEmpty())
                        {
                                //Remove element from this container
                                viewPanel.remove((Component) element);
                                //Remove element from map
                                //elementMap.get(tick)[index] = null;
                                //if(elementMap.get(tick)[getOtherIndex(index)] == null)
                                //        elementMap.remove(tick);
                        }
                }

        }

        private IElement createChordField(Chord chord, MutableValueNode<Double> frameXNode,
                                          MutableValueNode<Double> frameWidthNode)
        {
                return new ChordField(frameXNode, yNode, frameWidthNode, heightNode,
                                      chord, this);
        }

        private IElement createLyricField(Lyric lyric, MutableValueNode<Double> frameXNode,
                                          MutableValueNode<Double> frameWidthNode)
        {
                return new LyricField(frameXNode, yNode, frameWidthNode, heightNode,
                                      lyric, this);
        }

        private IElement createStaffSymbol(IStaffElement staffElement,
                                           MutableValueNode<Double> frameXNode,
                                           MutableValueNode<Double> frameWidthNode)
        {
                if(staffElement != null && !StaffPart.isValidMusicalTime(staffElement))
                {
                        IElement element = getStaffPart(staffElement.getBegin());
                        if(element != null)
                        {
                                ((StaffPart) element).addStaffElement(staffElement);
                                return null;
                        }
                }
                IElement element = new StaffPart(frameXNode, yNode,
                                                frameWidthNode, heightNode,
                                                staffBase, postStaffBase, this);
                ((StaffPart) element).addStaffElement(staffElement);
                return element;
        }

        private void createTie(Tie tie)
        {
                int beginTick = tie.getBegin();
                int endTick = tie.getEnd();

                if(!ties.containsKey(beginTick))
                        ties.put(beginTick, new HashMap<Tie, TieSymbol>());
                if(!ties.containsKey(endTick))
                        ties.put(endTick, new HashMap<Tie, TieSymbol>());

                TieSymbol tieSymbol = new TieSymbol();
                viewLayerPanel.addSymbol(tieSymbol);
                ties.get(beginTick).put(tie, tieSymbol);
                ties.get(endTick).put(tie, tieSymbol);
        }

        private void removeTie(Tie tie)
        {
                int beginTick = tie.getBegin();
                int endTick = tie.getEnd();

                TieSymbol tieSymbol = ties.get(beginTick).get(tie);
                //Remove symbol from panel
                viewLayerPanel.removeSymbol(tieSymbol);
                //Remove symbol from map
                ties.get(beginTick).remove(tie);
                ties.get(endTick).remove(tie);
                //Remove map if there are no symbols associated with it anymore
                if(ties.get(beginTick).isEmpty())
                        ties.remove(beginTick);
                if(ties.get(endTick).isEmpty())
                        ties.remove(endTick);
        }


        public Collection<TieSymbol> getTieSymbolsOnLeft(int tick)
        {
                return getTieSymbols(tick, true);
        }

        public Collection<TieSymbol> getTieSymbolsOnRight(int tick)
        {
                return getTieSymbols(tick, false);
        }

        private Collection<TieSymbol> getTieSymbols(int tick, boolean left)
        {    
                Collection<TieSymbol> tieSymbols = new HashSet<TieSymbol>();
                if(!ties.containsKey(tick))
                        return tieSymbols;
                for(Tie tie : ties.get(tick).keySet())
                {
                        if(left && tie.getBegin() == tick)
                                tieSymbols.add(ties.get(tick).get(tie));
                        if(!left && tie.getEnd() == tick)
                                tieSymbols.add(ties.get(tick).get(tie));
                }
                return tieSymbols;
        }

        private int getIndex(MusicalElement musicalElement)
        {
                if(musicalElement instanceof IStaffElement)
                        return StaffPart.isValidMusicalTime((IStaffElement) musicalElement) ? 1 : 0;
                return 1;
        }

        //private int getIndex(IElement element)
        //{
        //        return elementMap.get(element.getBegin())[0] == element ? 0 : 1;
        //}

        private int getOtherIndex(int index)
        {
                return index == 0 ? 1 : 0;
        }

        private IElement getStaffPart(int tick)
        {
                return relationBuilder.getMapper(this).getStaffPart(tick);
        }

        public void nodeAdded(Sequence sequence, MusicalElement musicalElement)
        {
                add(musicalElement);
        }

        public void nodeRemoved(Sequence sequence, MusicalElement musicalElement)
        {
                //removeMusicalElement(musicalElement);
        }

        
        public void nodeDifferenceChanged(Sequence sequence, MusicalElement musicalElement)
        {
                
        }

        public void staffElementAdded(IStaffElement staffElement)
        {
                add((MusicalElement) staffElement);
        }

        public void staffElementRemoved(IStaffElement staffElement)
        {
                remove((MusicalElement) staffElement);
        }        

        public IElement previousElement(IElement element)
        {
                return relationBuilder.getMapper(this).getPreviousElement(element);
        }

        public IElement nextElement(IElement element)
        {
                return relationBuilder.getMapper(this).getNextElement(element);
        }

        public IElement elementAbove(IElement element)
        {
                StaffView staffViewAbove = staffView.staffViewAbove();
                if(staffViewAbove == null)
                        return null;
                return staffViewAbove.getElement(element.getBegin(),
                        relationBuilder.getMapper(this).getIndex(element));
        }

        public IElement elementBelow(IElement element)
        {
                StaffView staffViewBelow = staffView.staffViewBelow();
                if(staffViewBelow == null)
                        return null;
                return staffViewBelow.getElement(element.getBegin(), 
                        relationBuilder.getMapper(this).getIndex(element));
        }
}
